#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/ptrace.h>
#include <linux/unistd.h>

#include <asm/segment.h>

#define _S(nr) (1 << ((nr)-1))

static void setup_frame(struct sigaction *sa, unsigned long **fp, unsigned long eip,
                        struct pt_regs *regs, int signr, unsigned long oldmask) {
    unsigned long *frame;

#define __CODE  ((unsigned long)(frame + 24))
#define CODE(x) ((unsigned long *)((x) + __CODE))
    frame = *fp;
    if (regs->ss != USER_DS)
        frame = (unsigned long *)sa->sa_restorer;
    frame -= 32;
    /* set up the "normal" stack seen by the signal handler (iBCS2) */
    put_fs_long(__CODE, frame);
    put_fs_long(signr, frame + 1);
    put_fs_long(regs->gs, frame + 2);
    put_fs_long(regs->fs, frame + 3);
    put_fs_long(regs->es, frame + 4);
    put_fs_long(regs->ds, frame + 5);
    put_fs_long(regs->edi, frame + 6);
    put_fs_long(regs->esi, frame + 7);
    put_fs_long(regs->ebp, frame + 8);
    put_fs_long((long)*fp, frame + 9);
    put_fs_long(regs->ebx, frame + 10);
    put_fs_long(regs->edx, frame + 11);
    put_fs_long(regs->ecx, frame + 12);
    put_fs_long(regs->eax, frame + 13);
    put_fs_long(current->tss.trap_no, frame + 14);
    put_fs_long(current->tss.error_code, frame + 15);
    put_fs_long(eip, frame + 16);
    put_fs_long(regs->cs, frame + 17);
    put_fs_long(regs->eflags, frame + 18);
    put_fs_long(regs->esp, frame + 19);
    put_fs_long(regs->ss, frame + 20);
    put_fs_long(0, frame + 21); /* 387 state pointer - not implemented*/
                                /* non-iBCS2 extensions.. */
    put_fs_long(oldmask, frame + 22);
    put_fs_long(current->tss.cr2, frame + 23);
    /* set up the return code... */
    put_fs_long(0x0000b858, CODE(0)); /* popl %eax ; movl $,%eax */
    put_fs_long(0x80cd0000, CODE(4)); /* int $0x80 */
    put_fs_long(__NR_sigreturn, CODE(2));
    *fp = frame;
#undef __CODE
#undef CODE
}

int do_signal(unsigned long oldmask, struct pt_regs *regs) {
    unsigned long mask = ~current->blocked;
    unsigned long handler_signal = 0;
    unsigned long *frame = NULL;
    unsigned long eip = 0;
    unsigned long signr;
    struct sigaction *sa;

    while ((signr = current->signal & mask)) {
        __asm__("bsf %2,%1\n\t"
                "btrl %1,%0"
                : "=m"(current->signal), "=r"(signr)
                : "1"(signr));
        sa = current->sigaction + signr;
        signr++;
        if (sa->sa_handler == SIG_DFL) {
            if (current->pid == 1)
                continue;
            switch (signr) {
            case SIGCONT:
            case SIGCHLD:
            case SIGWINCH:
                continue;

            case SIGSTOP:
            case SIGTSTP:
            case SIGTTIN:
            case SIGTTOU:
                current->state = TASK_STOPPED;
                current->exit_code = signr;
                if (!(current->p_pptr->sigaction[SIGCHLD - 1].sa_flags &
                      SA_NOCLDSTOP))
                    notify_parent(current);
                schedule();
                continue;

            case SIGQUIT:
            case SIGILL:
            case SIGTRAP:
            case SIGIOT:
            case SIGFPE:
            case SIGSEGV:
            default:
                current->signal |= _S(signr & 0x7f);
                do_exit(signr);
            }
        }
        /*
         * OK, we're invoking a handler
         */
        if (regs->orig_eax >= 0) {
            if (regs->eax == -ERESTARTNOHAND ||
                (regs->eax == -ERESTARTSYS && !(sa->sa_flags & SA_RESTART)))
                regs->eax = -EINTR;
        }
        handler_signal |= 1 << (signr - 1);
        mask &= ~sa->sa_mask;
    }
    if (regs->orig_eax >= 0 &&
        (regs->eax == -ERESTARTNOHAND ||
         regs->eax == -ERESTARTSYS ||
         regs->eax == -ERESTARTNOINTR)) {
        regs->eax = regs->orig_eax;
        regs->eip -= 2;
    }
    if (!handler_signal) /* no handler will be called - return 0 */
        return 0;
    eip = regs->eip;
    frame = (unsigned long *)regs->esp;
    signr = 1;
    sa = current->sigaction;
    for (mask = 1; mask; sa++, signr++, mask += mask) {
        if (mask > handler_signal)
            break;
        if (!(mask & handler_signal))
            continue;
        setup_frame(sa, &frame, eip, regs, signr, oldmask);
        eip = (unsigned long)sa->sa_handler;
        if (sa->sa_flags & SA_ONESHOT)
            sa->sa_handler = NULL;
        /* force a supervisor-mode page-in of the signal handler to reduce races */
        __asm__("testb $0,%%fs:%0"
                :
                : "m"(*(char *)eip));
        regs->cs = USER_CS;
        regs->ss = USER_DS;
        regs->ds = USER_DS;
        regs->es = USER_DS;
        regs->gs = USER_DS;
        regs->fs = USER_DS;
        current->blocked |= sa->sa_mask;
        oldmask |= sa->sa_mask;
    }
    regs->esp = (unsigned long)frame;
    regs->eip = eip; /* "return" to the first handler */
    current->tss.trap_no = current->tss.error_code = 0;
    return 1;
}
